package com.vision.admin.role.cl.ui;

import static com.vision.admin.cl.AdminServices.permissionSvc;
import static com.vision.admin.cl.AdminServices.roleSvc;

import java.util.ArrayList;
import java.util.List;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.editor.client.SimpleBeanEditorDriver;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiFactory;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.ui.Widget;
import com.sencha.gxt.core.client.Style.SelectionMode;
import com.sencha.gxt.data.shared.ListStore;
import com.sencha.gxt.widget.core.client.TabItemConfig;
import com.sencha.gxt.widget.core.client.button.TextButton;
import com.sencha.gxt.widget.core.client.container.AbstractHtmlLayoutContainer.HtmlData;
import com.sencha.gxt.widget.core.client.container.HtmlLayoutContainer;
import com.sencha.gxt.widget.core.client.container.VerticalLayoutContainer;
import com.sencha.gxt.widget.core.client.event.SelectEvent;
import com.sencha.gxt.widget.core.client.form.TextArea;
import com.sencha.gxt.widget.core.client.form.TextField;
import com.sencha.gxt.widget.core.client.grid.ColumnConfig;
import com.sencha.gxt.widget.core.client.grid.ColumnModel;
import com.sencha.gxt.widget.core.client.grid.Grid;
import com.sencha.gxt.widget.core.client.grid.GridView;
import com.sencha.gxt.widget.core.client.grid.GroupingView;
import com.sencha.gxt.widget.core.client.selection.SelectionChangedEvent;
import com.sencha.gxt.widget.core.client.selection.SelectionChangedEvent.SelectionChangedHandler;
import com.vision.admin.cl.AdminTasks;
import com.vision.admin.cl.resource.AdminResources;
import com.vision.admin.cl.ui.AbstractView;
import com.vision.admin.role.cl.PermissionProperties;
import com.vision.admin.role.cl.RoleProperties;
import com.vision.core.cl.BaseModule;
import com.vision.core.cl.Callback;
import com.vision.core.cl.CoreValidators;
import com.vision.core.cl.event.CreatedEvent;
import com.vision.core.cl.event.CreatedEvent.CreationHandler;
import com.vision.core.cl.event.RoleCreatedEvent;
import com.vision.core.cl.mvp.Presenter;
import com.vision.core.cl.ui.UI;
import com.vision.core.cl.ui.component.BasicGridView;
import com.vision.core.cl.ui.component.BasicGroupingView;
import com.vision.core.cl.ui.component.DualGridPanel;
import com.vision.core.cl.util.FieldUtils;
import com.vision.core.cm.data.ApplicationId;
import com.vision.core.cm.db.data.Permission;
import com.vision.core.cm.db.data.Role;

/**
 * 
 * @author Mark
 *
 */
public class RolesPanel extends AbstractView<Role> {
	
	interface Binder extends UiBinder<Widget, RolesPanel> { }
	// Interface used to bind the form fields to the User fields
	interface RoleDriver extends SimpleBeanEditorDriver<Role, RolesPanel> { }
	
	@UiField VerticalLayoutContainer mainPanel;
	
	@UiField Grid<Role> grid;
	
	@UiField TextField name;
	@UiField TextArea description;
	
	@Ignore @UiField TextButton saveBtn;
	@Ignore @UiField TextButton cancelBtn;
	
	@UiField DualGridPanel<Permission> permissionsPanel;
	
	public RolesPanel() {
		super(GWT.<Binder>create(Binder.class), GWT.<RoleDriver>create(RoleDriver.class));
		
		Presenter<Role> presenter = new RolesPresenter();
		setPresenter(presenter);
		presenter.setView(this);
		
		// Global Handlers -- should be registered only once
		BaseModule.addHandler(RoleCreatedEvent.TYPE, new CreationHandler<Role>() {
			@Override
			public void onCreated(CreatedEvent<Role> e) {
				grid.getStore().add(e.getObject());
			}
		});
		
		FieldUtils.regex(name, CoreValidators.USERNAME_REGEX);
		// TODO description validator
	}
	
	@Override
	protected void preBinderInvoke() {
		
	}
	
	@Override
	protected Grid<Role> getGrid() {
		return grid;
	}
	
	// UiFactory Methods %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

	@UiFactory
	Grid<Role> initializeGrid() {
		RoleProperties props = GWT.create(RoleProperties.class);

		List<ColumnConfig<Role, ?>> columns = new ArrayList<ColumnConfig<Role, ?>>();

		ColumnConfig<Role, String> name = UI.column(props.name(), 160, "Name");
		columns.add(name);

		ColumnConfig<Role, String> description = UI.column(props.description(), 300, "Description");
		columns.add(description);
		
		ColumnModel<Role> cm = new ColumnModel<Role>(columns);

		final Grid<Role> grid = new Grid<Role>(new ListStore<Role>(props.key()), cm);
		grid.setAllowTextSelection(false);
		grid.setHeight(150);
		grid.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
		
		GridView<Role> view = new BasicGridView<Role>();
		view.setEmptyText("There are no roles to display.");
		view.setAutoExpandColumn(description);
		grid.setView(view);
		
		// Add a listener to the grid
		grid.getSelectionModel().addSelectionChangedHandler(new SelectionChangedHandler<Role>() {
			@Override
			public void onSelectionChanged(SelectionChangedEvent<Role> event) {
				presenter.onSelect(getSelected());
			}
		});
		
		return grid;
	}
	
	@UiFactory
	HtmlLayoutContainer initializeFormContainer() {
		return new HtmlLayoutContainer(getFormLayout());
	}
	
	@UiFactory
	DualGridPanel<Permission> initializeRolesPanel() {
		DualGridPanel<Permission> panel = new DualGridPanel<Permission>() {
			
			Grid<Permission> createGrid() {
				PermissionProperties props = GWT.create(PermissionProperties.class);
				
				List<ColumnConfig<Permission, ?>> columns = new ArrayList<ColumnConfig<Permission, ?>>();
				
				final ColumnConfig<Permission, ApplicationId> applicationId = UI.column(props.applicationId(), 80, "Application ID"); 
				columns.add(applicationId);
				
				ColumnConfig<Permission, String> name = UI.column(props.name(), 120, "Name"); 
				columns.add(name);
				
				columns.add(UI.column(props.domain(), 80, "Domain"));
				
				Grid<Permission> grid = new Grid<Permission>(new ListStore<Permission>(props.key()), new ColumnModel<Permission>(columns));
				grid.setAllowTextSelection(false);
				grid.getSelectionModel().setSelectionMode(SelectionMode.MULTI);
				grid.setBorders(true);
				
				final GroupingView<Permission> view = new BasicGroupingView<Permission>("There are no permissions to display.");
				view.setAutoExpandColumn(name);
				view.setShowGroupedColumn(false);
				grid.setView(view);
				
				Scheduler.get().scheduleDeferred(new ScheduledCommand() {
					@Override
					public void execute() {
						view.groupBy(applicationId);
					}
				});
				
				return grid;
			}

			@Override
			protected Grid<Permission> createLeftGrid() {
				Grid<Permission> grid = createGrid();
				grid.getView().setEmptyText("There are no permissions to display.");
				return grid;
			}

			@Override
			protected Grid<Permission> createRightGrid() {
				Grid<Permission> grid = createGrid();
				grid.getView().setEmptyText("There role has no permissions.");
				return grid;
			}

			@Override
			protected void onLeftToRight(List<Permission> list) {
				assignRoles(list, true);
			}

			@Override
			protected void onRightToLeft(List<Permission> list) {
				unassignRoles(list, true);
			}

			@Override
			protected void allRightClicked(SelectEvent e) {
				// XXX We have to wrap into an ArrayList as the return value of #getAll()
				// which is a different type of List cannot be serialized by GWT. Weird.
				assignRoles(new ArrayList<Permission>(permissionsPanel.getLeftGrid().getStore().getAll()), false);
			}
			
			@Override
			protected void rightClicked(SelectEvent e) {
				assignRoles(permissionsPanel.getLeftGrid().getSelectionModel().getSelectedItems(), false);
			}
			
			@Override
			protected void leftClicked(SelectEvent e) {
				unassignRoles(permissionsPanel.getRightGrid().getSelectionModel().getSelectedItems(), false);
			}
			
			@Override
			protected void allLeftClicked(SelectEvent e) {
				// XXX We have to wrap into an ArrayList as the return value of #getAll()
				// which is a different type of List cannot be serialized by GWT. Weird.
				unassignRoles(new ArrayList<Permission>(permissionsPanel.getRightGrid().getStore().getAll()), false);
			}
			
		};
		
		panel.setLeftGridLabel("The list of Permissions in the system.");
		panel.setRightGridLabel("The list of Permissions the role has.");
		
		return panel;
	}
	
	@UiFactory
	HtmlData createHtmlData() {
		return new HtmlData("");
	}
	
	
	
	// UiHandlers %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
	
	// Called when the 'Refresh' button is clicked
	@UiHandler("refreshBtn")
	void refreshClicked(SelectEvent e) {
		presenter.onRefreshClicked(e);
	}
	
	// Called when the 'Add' button is clicked
	@UiHandler("addBtn")
	void addClicked(SelectEvent e) {
		presenter.onAddClicked(e);
	}
	
	// Called when the 'Delete' button is clicked
	@UiHandler("deleteBtn")
	void deleteClicked(SelectEvent e) {
		presenter.onDeleteClicked(e);
	}
	
	// Called when the 'Save' button is clicked
	@UiHandler("saveBtn")
	void saveClicked(SelectEvent e) {
		presenter.onSaveClicked(e);
	}
	
	// Called when the 'Cancel' button is clicked
	@UiHandler("cancelBtn")
	void cancelClicked(SelectEvent e) {
		presenter.onCancelClicked(e);
	}
	
	// Logic %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
	
	public void edit(final Role role) {
		driver.edit(role);
		
		saveBtn.setEnabled(role != null);
		cancelBtn.setEnabled(role != null);
		
		permissionsPanel.clearBoth();

		if (role == null)
			return;
		
		// Load the Permissions into the grids
		UI.mask(permissionsPanel, "Loading permissions...");
		permissionSvc().getAll(new Callback<List<Permission>>(permissionsPanel) {
			@Override
			protected void handleSuccess(List<Permission> roles) {
				permissionsPanel.clearBoth();
				
				// Remove those roles user already has
				roles.removeAll(role.getPermissions());
				
				permissionsPanel.getLeftGrid().getStore().addAll(roles);
				permissionsPanel.getRightGrid().getStore().addAll(role.getPermissions());
			}
		});
	}
	
	private void assignRoles(final List<Permission> permissions, final boolean viaDND) {
		if (permissions.isEmpty())
			return;
		
		final Role role = driver.flush();
		if (role == null)
			return;
		
		UI.mask(mainPanel, "Assigning permissions to role...");
		roleSvc().assignPermissions(role, permissions, new Callback<List<Permission>>(mainPanel) {
			@Override
			protected void handleSuccess(List<Permission> result) {
				role.addPermissions(result);
				grid.getStore().update(role);
				
				if (viaDND) return;
				// If assignPermissions was not called via DND, add the roles to rolesPanel.getRightGrid()
				for (Permission role : result) {
					permissionsPanel.getLeftGrid().getStore().remove(role);
					permissionsPanel.getRightGrid().getStore().add(role);
				}
			}
			
			@Override
			protected void handleFailure(Throwable t) {
				if (!viaDND) return;
				// If assignPermissions was called via DND, return the roles back to rolesPanel.getLeftGrid()
				for (Permission role : permissions) {
					permissionsPanel.getRightGrid().getStore().remove(role);
					permissionsPanel.getLeftGrid().getStore().add(role);
				}
			}
		});
	}
	
	private void unassignRoles(final List<Permission> permissions, final boolean viaDND) {
		if (permissions.isEmpty())
			return;
		
		final Role role = driver.flush();
		if (role == null)
			return;
		
		UI.mask(mainPanel, "Unassigning permissions from role...");
		roleSvc().unassignPermissions(role, permissions, new Callback<List<Permission>>(mainPanel) {
			@Override
			protected void handleSuccess(List<Permission> result) {
				role.removePermissions(result);
				grid.getStore().update(role);
				
				if (viaDND) return;
				// If unassignPermissions was not called via DND, add the roles to the rolesPanel.getLeftGrid()
				for (Permission role : result) {
					permissionsPanel.getRightGrid().getStore().remove(role);
					permissionsPanel.getLeftGrid().getStore().add(role);
				}
			}
			
			@Override
			protected void handleFailure(Throwable t) {
				if (!viaDND) return;
				// If unassignPermissions was called via DND, return the roles back to rolesPanel.getRightGrid()
				for (Permission role : permissions) {
					permissionsPanel.getLeftGrid().getStore().remove(role);
					permissionsPanel.getRightGrid().getStore().add(role);
				}
			}
		});
	}

	// ITabItemPanel Methods %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
	
	@Override
	public String getPanelId() {
		return AdminTasks.ROLES.getId();
	}
	
	@Override
	public TabItemConfig getConfig() {
		return getConfig("Roles", true, AdminResources.INSTANCE.roles());
	}
	
	// Helper Methods %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
	
	private static native String getFormLayout() /*-{
	    return [ 
	    '<table width=100% cellpadding=0 cellspacing=0>',
		    '<tr><td class=name width=50% valign=top></td><td class=desc width=50% height=100 valign=top></td></tr>',
	    '</table>'
		].join("");
	}-*/;

}
